# -*- coding: UTF-8 -*-
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from threading import Lock
from collections import OrderedDict
from Code.Utils.sc_func import Common
from Code.View.sc_global import Global
from Code.View.Datamation.sc_provide import view_gate


def center_window(master, width_flag=0.5, height_flag=0.382):   # 0.382
    master.withdraw()
    master.update()
    current_window_width = master.winfo_width()
    current_window_height = master.winfo_height()
    screen_width = master.winfo_screenwidth()
    screen_height = master.winfo_screenheight()
    suitable_location_x = int((screen_width - current_window_width) * width_flag)
    suitable_location_y = int((screen_height - current_window_height) * height_flag)
    master.geometry('+{}+{}'.format(suitable_location_x, suitable_location_y))
    master.deiconify()


class WidgetTip:
    _toplevel = None

    @classmethod
    def info(cls, info):
        messagebox.showinfo('Information', info)

    @classmethod
    def error(cls, info):
        messagebox.showerror('Error', info)

    @classmethod
    def ask(cls, info):
        return messagebox.askokcancel('Confirm', info)

    @classmethod
    def warn(cls, info):
        messagebox.showwarning('Warning', info)

    @classmethod
    def enter_tips(cls, widget, text, width=20, height=20):
        def enter(event):
            cls._enter_tip(widget, text, width, height)

        def leave(event):
            cls._hide_tip()
        widget.bind('<Enter>', enter)
        widget.bind('<Leave>', leave)

    @classmethod
    def _enter_tip(cls, widget, text, width, height):
        if cls._toplevel:
            return
        x, y, cx, cy = widget.bbox("insert")
        x = x + widget.winfo_rootx() + width
        y = y + widget.winfo_rooty() - height
        cls._toplevel = tw = tk.Toplevel(widget)
        tw.overrideredirect(1)
        tw.wm_attributes('-topmost', 1)
        tw.geometry("+%d+%d" % (x, y))
        tk.Label(tw, text=text, font=("tahoma", "8"), justify='left', background="#ffffe0",
                 relief=tk.SOLID, borderwidth=1).pack(ipadx=1)

    @classmethod
    def _hide_tip(cls):
        if cls._toplevel:
            cls._toplevel.destroy()
            cls._toplevel = None


class ProgressBar(object):
    """ 比例条 """
    def __init__(self, master, width=180, height=18, bg='White', row=0, column=0):
        self.canvas_bar = None
        self.canvas_shape = None
        self.canvas_text = None
        self.width = width
        self.height = height
        self.canvas_bar = tk.Canvas(master, bg=bg, width=self.width, height=self.height)
        self.canvas_shape = self.canvas_bar.create_rectangle(0, 0, 0, self.height, fill='LightSkyBlue')
        self.canvas_text = self.canvas_bar.create_text(self.width/2, self.height/2+2, text='0%')
        self.canvas_bar.grid(row=row, column=column, padx=10)

    def update(self, percent, success=True, colors=False):
        prog_len = int(self.width * percent / 100) + 1
        color = 'LightSkyBlue'   # 默认进度条颜色
        if colors:     # 程度颜色
            if 60 <= percent < 70:
                color = 'Gold'
            elif 70 <= percent < 80:
                color = 'Coral'
            elif 80 <= percent < 90:
                color = 'OrangeRed'
            elif 90 <= percent <= 100:
                color = 'Red3'
        elif not success:    # 失败
            color = 'Red'
        self.canvas_bar.coords(self.canvas_shape, (0, 0, prog_len, self.height+2))
        self.canvas_bar.itemconfig(self.canvas_text, text='%s%%' % percent)
        self.canvas_bar.itemconfig(self.canvas_shape, fill=color, outline=color)

    def destroy(self):
        self.canvas_bar.destroy()


class LabelButton(object):
    """ Radiobutton实现的侧边菜单栏 """
    def __init__(self, master, name, shell, intvar, text, command):
        self.name = name
        self.shell = shell
        self.command = command
        self.button = tk.Radiobutton(master,
                                     selectcolor='SkyBlue2',
                                     fg='Snow',
                                     bg='SkyBlue4',
                                     variable=intvar,
                                     width=20,
                                     bd=0,
                                     indicatoron=0,
                                     value=name,
                                     text=text,
                                     justify='left',
                                     font=('宋体', 14),
                                     command=self._click)
        self.button.bind("<Enter>", self.enter)
        self.button.bind("<Leave>", self.leave)
        self.button.pack(fill='both')

    def enter(self, event=None):
        self.button['fg'] = 'Brown1'
        self.button['bg'] = 'SkyBlue3'

    def leave(self, event=None):
        self.button['fg'] = 'Snow'
        self.button['bg'] = 'SkyBlue4'
    
    def pack(self):
        self.button.pack(ipady=12)
        
    def _click(self, event=None):
        self.command(self.name, self.shell)


class LabelEntry(object):
    def __init__(self, master, text, default='', row=0, column=0, lab_width=30, en_width=32, show=''):
        tk.Label(master, text=text, width=lab_width, anchor='e').grid(row=row, column=column)
        self.entry_var = tk.StringVar()
        self.entry = ttk.Entry(master, width=en_width, textvariable=self.entry_var)
        self.entry.grid(row=row, column=column + 1, padx=10, pady=1, sticky='w')
        self.entry_var.set(default)
        self.entry['show'] = show

    def get(self):
        return self.entry_var.get()

    def set(self, val):
        self.entry_var.set(val)


class LabelCombobox(object):
    def __init__(self, master, text, values, default='', row=0, column=0, lab_width=30, com_width=30):
        tk.Label(master, text=text, width=lab_width, anchor='e').grid(row=row, column=column)
        self.Combobox = ttk.Combobox(master, values=values, width=com_width, state='readonly')
        self.Combobox.grid(row=row, column=column + 1, padx=10, pady=1)
        index = values.index(default) if (default and default in values) else 0
        self.Combobox.current(index)

    def get(self):
        return self.Combobox.get()


class ColorButton(object):
    """ 封装button，个性化外观 """

    def __init__(self, master, text, command, size=10, width=10):
        self.master = master
        self.button = tk.Button(
            master,
            text=text,
            activebackground=Global.G_DEFAULT_COLOR,
            activeforeground='Red',
            disabledforeground='Red',
            fg='Gray23',  # 'DodgerBlue4',
            bg=Global.G_DEFAULT_COLOR,
            font=(Global.G_DEFAULT_FONT, size, 'bold'),
            width=width,
            bd=0,
            relief='groove',
            command=command
        )
        # self.button.bind("<Enter>", self.enter)
        # self.button.bind("<Leave>", self.leave)
        self.button.pack(side='left')
        self.disable(False)

    def enter(self, event=None):
        self.button['bg'] = 'Grey27'  # 'DodgerBlue4'
        self.button['fg'] = Global.G_DEFAULT_COLOR

    def leave(self, event=None):
        self.button['bg'] = Global.G_DEFAULT_COLOR
        self.button['fg'] = 'Gray23'  # 'DodgerBlue4'

    def disable(self, flag):
        if flag:
            self.button['state'] = 'disable'
            self.button.unbind("<Enter>")
            self.button.bind("<Leave>")
        else:
            self.button['state'] = 'normal'
            self.button.bind("<Enter>", self.enter)
            self.button.bind("<Leave>", self.leave)


class TitleFrame(object):
    """ 带有主题信息的Frame """
    def __init__(self, master, width, height, title='', center=False, color=Global.G_FM_TITLE_COLOR):
        head_height = 25 if title != '' else 0
        _master = tk.LabelFrame(master, width=width, height=height+head_height)
        _master.pack(anchor='w')
        _master.pack_propagate(0)
        if title != '':
            head_fm = tk.Frame(_master, height=head_height)
            head_fm.pack(fill='x')
            anchor = 'center' if center else 'w'
            text = "  %s  " % title
            label = tk.Label(head_fm, text=text, font=(Global.G_DEFAULT_FONT, 10, 'bold'), anchor=anchor, bg=color)
            label.pack(fill='both')
        self.body = tk.Frame(_master, height=height)
        self.body.pack(fill='both')

    def master(self):
        return self.body


class ScrollFrame(object):
    """ 带有滑块的窗体Frame """
    def __init__(self, master, width=None, height=None):
        kw = {'width': width, 'height': height} if width and height else {}
        canvas = tk.Canvas(master, **kw)
        scrollbar = ttk.Scrollbar(master, orient="vertical", command=canvas.yview)
        self.body = tk.Frame(canvas)
        self.body.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        self.body.pack(fill='both')
        canvas.create_window((0, 0), window=self.body, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")

    def master(self):
        return self.body


class MenuBar(object):
    """ 菜单栏 (顶部或右键)
    data eg: [('A', 'a'), '-', ('B', [('C', 'c'), ('D', [('E', 'e', ...)])])]
    """
    def __init__(self, master, data, callback, popup=False, event=None):
        self.callback = callback
        root = tk.Menu(master, tearoff=0) if popup else master
        [self.sub_tree(root, item) for item in data]
        if popup:
            root.post(event.x_root, event.y_root)

    def sub_tree(self, root, item):
        if item == '-':
            root.add_separator()
            return
        name, key = item
        if isinstance(key, str):
            root.add_command(label=name, command=lambda x=key: self.callback(x))
        elif isinstance(key, list):
            menu = tk.Menu(root, tearoff=0)
            root.add_cascade(label=name, menu=menu)
            [self.sub_tree(menu, k) for k in key]


class ToolBar(object):
    """ 工具栏
    data eg: [('A', 'a'), ('B', 'b'), ...]
    """
    def __init__(self, master, data, callback):
        for name, key, index in data:
            index = [name, key][index]
            btn = ttk.Button(master,
                             image=view_gate.query_photo_image_data(key),
                             style="App.TButton",
                             command=lambda x=index: callback(x))
            btn.pack(side='left')
            WidgetTip.enter_tips(btn, name)


class NavigationBar(object):
    """ 导航栏
    trees eg: [('A', 'a', []), ('b', 'b', [('C', 'c', []), ...]), ...]
    """
    def __init__(self, master, trees, callback):
        self.callback = callback
        self.all_nodes = OrderedDict()
        self.is_expand = False
        self.curr_node = None
        self.treeview = ttk.Treeview(master, height=50, show="tree", selectmode='browse')
        self.treeview.tag_configure('tree.sub', font=('宋体', 12))
        self.treeview.tag_configure('tree.root', font=('宋体', 12, 'bold'))
        self.treeview.bind('<<TreeviewSelect>>', self.select_handle)
        self.treeview.pack(fill='both')
        [self.sub_tree(root) for root in trees]

    def sub_tree(self, root, id=''):
        name, img, sub = root
        image = view_gate.query_photo_image_data(img)
        tag = 'tree.root' if sub else 'tree.sub'
        id = self.treeview.insert(id, 'end', text=' %s' % name, image=image, tags=(tag, 'simple'), values=name)
        self.all_nodes[name] = id
        for _sub in sub:
            self.sub_tree(_sub, id)

    def expand_trees(self):
        self.is_expand = not self.is_expand
        for _, id in self.all_nodes.items():
            self.treeview.item(id, open=self.is_expand)

    def select_handle(self, event=None):
        id = self.treeview.selection()[-1]
        # name 中有空格时元祖元素会有多个, 需要拼接一下
        name = ' '.join(self.treeview.item(id, "values"))
        self.curr_node = name
        self.callback(name)

    def selection(self, name):
        id = self.all_nodes[name]
        self.treeview.see(id)
        self.treeview.focus(id)
        self.treeview.selection_set(id)    # 触发select事件 #

    def prev(self):
        if not self.curr_node:
            return
        names = list(self.all_nodes.keys())
        index = names.index(self.curr_node)
        if index == 0:
            return
        prev_node = names[index - 1]
        self.selection(prev_node)

    def next(self):
        if not self.curr_node:
            return
        names = list(self.all_nodes.keys())
        index = names.index(self.curr_node)
        if index == len(names) - 1:
            return
        next_node = names[index + 1]
        self.selection(next_node)


class ConfirmEntry(object):
    """ 带有确认按钮的输入框 """
    def __init__(self, callback, text=None, value='', options=None, width=30):
        def close(event=None):
            top.destroy()

        def confirm(event=None):
            input = en.get()
            if not input:
                WidgetTip.error(view_gate.query_language_data('INPUT', 'CANT_NULL'))
                return
            callback(input, [bool(int(v.get())) for v in vars if v])
            close()

        top = tk.Toplevel()
        top.title('')
        top.resizable(False, False)
        fm1 = tk.Frame(top)
        fm2 = tk.Frame(top)
        fm3 = tk.Frame(top)
        fm1.pack()
        fm2.pack()
        fm3.pack()
        en = LabelEntry(fm1, text, value, lab_width=20, en_width=width)
        en.entry.bind('<Return>', confirm)
        vars = []
        if options:
            for opt in options:
                vars.append(tk.IntVar())
                ttk.Checkbutton(fm2, text=opt, variable=vars[-1], width=20).pack()
        btn1 = ttk.Button(fm3, text=' OK ', command=confirm)
        btn2 = ttk.Button(fm3, text=' Cancel ', command=close)
        btn1.pack(side='left', padx=20, pady=10)
        btn2.pack(side='right', padx=20, pady=10)
        center_window(top)


class InfoText(object):
    """ 消息提示栏 """
    def __init__(self, master, height=40):
        self.master = master
        self.infotext = None
        self.lock = Lock()
        self.init_frame(height)

    def init_frame(self, height):
        self.infotext = scrolledtext.ScrolledText(self.master, font=(Global.G_TEXT_FONT, 10), relief='ridge',
                                                  bg=Global.G_DEFAULT_COLOR, height=height)
        self.infotext['stat'] = 'disabled'
        self.infotext.pack(fill='both')

    def get_text(self):
        return self.infotext.get('1.0', 'end')

    def clear(self):
        self.infotext['stat'] = 'normal'
        self.infotext.delete('1.0', 'end')
        self.infotext['stat'] = 'disabled'

    def insert_text(self, info, color=False, see_end=False, store=False):
        # 多线程打印可能会串行等问题, 加锁
        self.lock.acquire()
        try:
            self.infotext['stat'] = 'normal'
            self.infotext.insert('end', '{}\n'.format(info))
            if color:
                start = self.infotext.get('1.0', 'end').count('\n') - 1
                end = start + len(info.split('\n')) - 1
                self.infotext.tag_add('tag-%s' % end, '%s.0' % start, '%s.end' % end)
                self.infotext.tag_config('tag-%s' % end, foreground=color)
            if see_end:
                self.infotext.see('end')
            if store:
                lines = self.infotext.get('1.0', 'end')
                count = lines.count('\n')
                """ 大于1000行时, 转储所有内容到文件,并清空text """
                if count > 1000:
                    store_file = view_gate.query_env_define_data('G_INFO_PATH')
                    new_info = '* Previous records have been dumped to {} *\n'.format(store_file)
                    Common.write_append_file(store_file, lines)
                    for n in range(1, count):
                        self.infotext.tag_delete('tag-%s' % n)
                    self.infotext.delete('1.0', 'end')
                    self.infotext.insert('end', new_info)
                    self.infotext.tag_add('tag-1', '1.0', '1.end')
                    self.infotext.tag_config('tag-1', font=(Global.G_TEXT_FONT, 12, 'bold'))
            self.infotext['stat'] = 'disabled'
        finally:
            self.lock.release()

    def insert_progress(self, key, value):
        self.lock.acquire()
        try:
            fm = tk.Frame(self.infotext, bg=Global.G_DEFAULT_COLOR)
            tk.Label(fm, text=key, font=(Global.G_TEXT_FONT, 11),
                     bg=Global.G_DEFAULT_COLOR).grid(row=0, column=0, pady=3)
            ProgressBar(fm, width=320, height=20, column=1).update(value, colors=True)
            self.infotext.window_create('end', window=fm)
        finally:
            self.lock.release()

    def insert_image(self, img):
        self.lock.acquire()
        try:
            lab = tk.Label(self.infotext, image=img, bg=Global.G_DEFAULT_COLOR)
            self.infotext.window_create('end', window=lab)
        finally:
            self.lock.release()

    def insert_markdown(self, lines):
        def color():
            if line.startswith('<RED> '):
                return line[6:], 'Red'
            elif line.startswith('<GREEN> '):
                return line[8:], 'Green'
            elif line.startswith('<BLUE> '):
                return line[7:], 'Blue'
            return line, 'Black'

        def title():
            if line.startswith('# '):
                return line[2:], 16, 'bold'
            elif line.startswith('## '):
                return line[3:], 15, 'bold'
            elif line.startswith('### '):
                return line[4:], 14, 'bold'
            elif line.startswith('#### '):
                return line[5:], 13, 'bold'
            elif line.startswith('##### '):
                return line[6:], 12, 'bold'
            elif line.startswith('###### '):
                return line[7:], 11, 'bold'
            return line, 10, ''

        def style():
            if line.startswith('***') and line.endswith('***'):
                return line[3:-3], 'bold', 'italic'
            elif line.startswith('**') and line.endswith('**'):
                return line[2:-2], 'bold', ''
            elif line.startswith('*') and line.endswith('*'):
                return line[1:-1], f_bold, 'italic'
            return line, f_bold, ''

        def types():
            ckb_style = {
                'master': self.infotext,
                'font': (Global.G_TEXT_FONT, f_size, ' '.join([f_bold, f_italic])),
                'fg': f_color,
                'bg': 'Snow',
                # 'state': 'disabled'
            }
            if line.startswith(('- [ ] ', ': - [ ] ')):
                index = 6
                if line.startswith(':'):
                    index = 8
                    self.infotext.insert('end', '\t')
                ckb = tk.Checkbutton(text=line[index:], **ckb_style)
                ckb.deselect()
                self.infotext.window_create('end', window=ckb)
                self.infotext.insert('end', '\n')
                return None, None, None
            elif line.startswith(('- [x] ', ': - [x] ')):
                index = 6
                if line.startswith(':'):
                    index = 8
                    self.infotext.insert('end', '\t')
                ckb = tk.Checkbutton(text=line[index:], **ckb_style)
                ckb.select()
                self.infotext.window_create('end', window=ckb)
                self.infotext.insert('end', '\n')
                return None, None, None
            elif line.startswith('> - '):
                return '▌ • %s' % line[4:], 'Gray' if f_color == 'Black' else f_color, f_size
            elif line.startswith('> : '):
                return '▌ \t%s' % line[4:], 'Gray' if f_color == 'Black' else f_color, f_size
            elif line.startswith('> '):
                return '▌  %s' % line[2:], 'Gray' if f_color == 'Black' else f_color, f_size
            elif line.startswith(': - '):
                return '\t• %s' % line[4:], f_color, f_size
            elif line.startswith(': '):
                return '\t%s' % line[2:], f_color, f_size
            elif line.startswith('- ') or line.startswith('* ') or line.startswith('+ '):
                return '• ' + line[2:], f_color, max(11, f_size)
            return line, f_color, f_size

        self.lock.acquire()
        try:
            self.infotext['stat'] = 'normal'
            count = self.infotext.get('1.0', 'end').count('\n')
            for line in lines.split('\n'):
                line, f_color = color()           # 字体颜色
                line, f_size, f_bold = title()    # 字体大小
                line, f_bold, f_italic = style()  # 字体样式
                line, f_color, f_size = types()   # 其他类型
                if line is None:
                    continue
                font = (Global.G_TEXT_FONT, f_size, ' '.join([f_bold, f_italic]))
                self.infotext.insert('end', '{}\n'.format(line))
                self.infotext.tag_add("tag-%s" % count, "%s.0" % count, "%s.end" % count)
                self.infotext.tag_config("tag-%s" % count, font=font, foreground=f_color)
                count += 1
            self.infotext['stat'] = 'disabled'
        finally:
            self.lock.release()

